feat: flow mode for tasks#230
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughAdds a "flow mode" to the tasks page with a UI switch and vibration feedback, a store flag and ordered active-task list, a new API endpoint to fetch not-completed tasks, and a DB repository method to query tasks where Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant UI as Task Page (my.vue)
participant Store as Task Store
participant API as /api/task/list/not-completed
participant DB as Task repository
User->>UI: Toggle Flow Mode Switch
UI->>UI: call vibrate()
UI->>Store: set isInFlowMode
Store->>Store: updateNotCompleted() (async)
Store->>API: GET /task/list/not-completed
API->>DB: findAllNotCompleted()
DB-->>API: Task[] (notCompleted)
API-->>Store: Task[]
Store->>Store: compute myTasksOrderedByDate
Store-->>UI: notCompletedTasks / myTasksOrderedByDate
UI->>UI: render TaskActiveCard list (or empty message) when flow mode
UI-->>User: Updated UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (9)
packages/database/src/repository/task.ts (1)
20-25: Add pagination/limit parity and plan an index for performance.Mirror findAll()’s limit to prevent unbounded scans and responses. Also consider a partial index to serve this query efficiently.
- Code change (if you keep the generic method):
- orderBy: (tasks, { desc }) => desc(tasks.updatedAt), + orderBy: (tasks, { desc }) => desc(tasks.updatedAt), + limit: 1500,
- DB index (PostgreSQL):
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_active_updated_at ON tasks (updated_at DESC) WHERE completed_at IS NULL;apps/atrium-telegram/app/pages/task/my.vue (3)
32-42: Load data when enabling flow mode to avoid empty flicker.If flow mode is toggled before not-completed data arrives, the page shows “Активных задач нет”. Trigger a refresh on toggle-on and add an aria label.
- <USwitch - v-model="taskStore.isInFlowMode" + <USwitch + v-model="taskStore.isInFlowMode" size="xl" color="secondary" :label="taskStore.isInFlowMode ? `Режим "Поток"` : 'Режим "Списки"'" :ui="{ root: 'items-center', label: 'ml-1.5 text-base/5 font-semibold', }" - @change="vibrate()" + aria-label="Переключить режим задач" + @change="(val: boolean) => { vibrate(); if (val && !taskStore.notCompletedTasks.length) taskStore.updateNotCompleted?.() }" />Note: expose
updateNotCompletedfrom the store (see store comment).
61-77: Show a loading state for flow mode.Consider a small loader/skeleton while not-completed tasks are being fetched to prevent the “no tasks” flash.
15-18: Move inline toggle logic into a store action.Keep UI markup clean and centralize state transitions (e.g.,
taskStore.toggleTodayOnly()that also disables flow mode).apps/atrium-telegram/app/stores/task.ts (5)
30-39: Date sorting: avoid timezone pitfalls fromnew Date('YYYY-MM-DD').
new Date('2025-10-17')parses as UTC; ordering can be off vs local. Use string compare or@internationalized/datefor day-precision.- const myTasksOrderedByDate = computed(() => { - const filterByMe = (task: Task) => task.performerId === userStore.id - const sortByDateAsc = (a: Task, b: Task) => a.date && b.date ? new Date(a.date).getTime() - new Date(b.date).getTime() : 0 - const myTasks = notCompletedTasks.value.filter(filterByMe) - const tasksWithDate = myTasks.filter((task) => task.date).sort(sortByDateAsc) - const tasksWithoutDate = myTasks.filter((task) => !task.date) - return [...tasksWithDate, ...tasksWithoutDate] - }) + const myTasksOrderedByDate = computed(() => { + const mine = notCompletedTasks.value.filter((t) => t.performerId === userStore.id) + const withDate = mine.filter((t) => !!t.date).sort((a, b) => a.date!.localeCompare(b.date!)) + const withoutDate = mine.filter((t) => !t.date) + return [...withDate, ...withoutDate] + })
44-60: Initialize after data is ready to prevent flow-mode flicker.
isInitializedis set beforeupdateCompleted()/updateNotCompleted(). Users can switch to flow mode and briefly see empty state. Set the flag after both calls (or add a separate “isNotCompletedLoaded”).- lists.value = data - - isInitialized.value = true - - await updateCompleted() - await updateNotCompleted() + lists.value = data + await Promise.all([updateCompleted(), updateNotCompleted()]) + isInitialized.value = trueIf independent failure handling is desired, use
Promise.allSettledand still flipisInitializedonce at least lists + one of the datasets is ready.
96-118: Expose updater for on-demand refresh.Export
updateNotCompletedso the page can refresh when toggling flow mode.return { lists, tasks, notCompletedTasks, isTodayOnly, isInFlowMode, isInitialized, @@ myTasksOrderedByDate, update, + updateNotCompleted, setAsFocused, setAsUnfocused, }
96-118: Headers duplication: factor out auth headers.Minor: DRY the repeated Authorization header creation to a small helper.
17-20: State naming clarity.Consider
activeTasksinstead ofnotCompletedTasksfor positive naming in UI-facing code.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/atrium-telegram/app/pages/task/my.vue(3 hunks)apps/atrium-telegram/app/stores/task.ts(5 hunks)apps/atrium-telegram/server/api/task/list/not-completed.get.ts(1 hunks)packages/database/src/repository/task.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/database/src/repository/task.ts (1)
packages/database/src/tables.ts (1)
tasks(266-278)
apps/atrium-telegram/server/api/task/list/not-completed.get.ts (1)
apps/web-app/server/services/db.ts (1)
db(164-164)
apps/atrium-telegram/app/stores/task.ts (1)
packages/database/src/repository/task.ts (1)
Task(6-150)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
| import { db } from '@roll-stack/database' | ||
|
|
||
| export default defineEventHandler(async () => { | ||
| return db.task.findAllNotCompleted() | ||
| }) |
There was a problem hiding this comment.
Route returns all users’ active tasks; enforce auth and scope.
The handler exposes every not-completed task. Read the user from the TMA Authorization header/context and query only their tasks (or tasks in lists they can access). Also prefer return await for better stack traces.
Example:
-import { db } from '@roll-stack/database'
-
-export default defineEventHandler(async () => {
- return db.task.findAllNotCompleted()
-})
+import { db } from '@roll-stack/database'
+// import your existing auth util; adjust to your codebase
+// e.g., const user = await requireUser(event) or readTelegramUser(event)
+export default defineEventHandler(async (event) => {
+ const user = await requireUser(event) // adapt to your auth helper
+ return await db.task.findAllNotCompletedByPerformer(user.id)
+})If you already attach the user to event.context, use it directly. Consider projecting only needed fields to reduce payload.
Committable suggestion skipped: line range outside the PR's diff.
🧩 Analysis chain
Verify no other routes expose unscoped task listings.
Search for unscoped usages to avoid similar leaks.
🏁 Script executed:
#!/bin/bash
# Find unscoped task list endpoints/usages
rg -nP -C2 'findAllNotCompleted\(|completedAt\s*[,)]\s*=>\s*isNull' apps/ packages/ | sed -n '1,200p'
# Look for auth helpers to wire here
rg -nP -C2 'requireUser\(|getUser.*event|telegram|initData|tma ' --type=ts -- apps/ | sed -n '1,200p'Length of output: 15076
🏁 Script executed:
#!/bin/bash
# Check atrium-telegram app structure and auth
fd '(middleware|auth)' apps/atrium-telegram/server/ -type f
# Look for any existing auth in atrium-telegram
rg -l 'getUserSession|validateTelegramData|requireUser|getHeader' apps/atrium-telegram/server/ --type=tsLength of output: 220
🏁 Script executed:
#!/bin/bash
# Read atrium-telegram auth middleware to identify auth pattern
cat apps/atrium-telegram/server/middleware/01.auth.tsLength of output: 1894
Scoped task listing is protected by auth middleware but still returns unscoped results—specify userId scope.
The endpoint is covered by atrium-telegram's auth middleware (which enforces valid Telegram user), but findAllNotCompleted() returns all incomplete tasks regardless of the authenticated user. Scope results to event.context.user.id or add a userId parameter to the database method.
No other routes expose findAllNotCompleted() (grep confirmed single usage).
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/task/list/not-completed.get.ts lines 1-5, the
handler calls db.task.findAllNotCompleted() and returns all users' incomplete
tasks despite the route being protected by auth; change the handler to scope
results to the authenticated user by reading the id from event.context.user.id
and passing it into the DB call (e.g., db.task.findAllNotCompleted({ userId:
event.context.user.id })), or if the DB API lacks that param, add a userId
argument to findAllNotCompleted and update its query to filter by userId before
returning results.
| static async findAllNotCompleted() { | ||
| return useDatabase().query.tasks.findMany({ | ||
| where: (tasks, { isNull }) => isNull(tasks.completedAt), | ||
| orderBy: (tasks, { desc }) => desc(tasks.updatedAt), | ||
| }) | ||
| } |
There was a problem hiding this comment.
Security: scope the query to the requesting user (avoid leaking all tasks).
This returns every not-completed task across the system. The new API exposes it directly. Scope by performerId (and/or accessible lists) and add a limit to avoid large responses.
Apply something like:
- static async findAllNotCompleted() {
- return useDatabase().query.tasks.findMany({
- where: (tasks, { isNull }) => isNull(tasks.completedAt),
- orderBy: (tasks, { desc }) => desc(tasks.updatedAt),
- })
- }
+ static async findAllNotCompletedByPerformer(userId: string, limit = 1500) {
+ return useDatabase().query.tasks.findMany({
+ where: (tasks, { and, isNull, eq }) =>
+ and(isNull(tasks.completedAt), eq(tasks.performerId, userId)),
+ orderBy: (tasks, { desc }) => desc(tasks.updatedAt),
+ limit,
+ })
+ }Then update the API route to call the scoped method. If list membership is the authority boundary, replace the performer filter with an existence check against lists the user can access.
🤖 Prompt for AI Agents
In packages/database/src/repository/task.ts around lines 20–25, the current
findAllNotCompleted() returns all not-completed tasks and may leak tasks across
users; change it to accept the calling user context (e.g., performerId and/or
accessibleListIds) and a safe limit, then scope the query to either
tasks.performerId === performerId or tasks that belong to any of the provided
accessibleListIds (use an existence check if list membership is the authority),
include a default max limit (e.g., 100) and allow callers to pass a smaller
limit, and update the API route to call this new scoped method instead of the
unscoped one.



Summary by CodeRabbit
New Features
Improvements